/*
 * $AIST_Release: 0.9.0 $
 * Copyright 2011 Information Technology Research Institute, National
 * Institute of Advanced Industrial Science and Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package generator

import java.io.File
import java.io.PrintWriter

import parser.DataTypes.DataType
import parser.DataTypes.ArrayType
import parser.DataTypes.TupleType
import com.google.protobuf.Descriptors.FieldDescriptor
import com.google.protobuf.Descriptors.Descriptor
import scala.collection.JavaConversions._

/**
 * The module to generate "Converters.java".
 * In "Converters.java", converters between ProtocolBuffers message and byte string is defined.
 */
object Converters {
  
  import Generator.getMembers

  /*
   * TODO: コードジェネレータが定義文を出力する構造体のようなクラスと
   *       ProtocolBuffersのMessageの間の変換でコピーが発生する。これ
   *       が速度低下の原因になると思う。
   */

  /**
   * Generates "Converters.java".
   */
  def generate(dir: File, msgs: List[Descriptor], typeM: TypeMapper): (File, String) = {
    Generator.genSrc(dir, "Converters") { print(_, msgs, typeM) }
  }

  /**
   * Prints contents of "Converters.java".
   */
  private def print(pw: PrintWriter,
            msgs: List[Descriptor],
            typeM: TypeMapper): Unit = {

    /**
     * Prints fields.
     * The fields are Descriptor and FieldDescriptor.
     */
    def printFields(msgs: List[Descriptor]): Unit = {
      for (msg <- msgs) {
        pw.println("private Descriptor %s;".format(msgDescName(msg)))
        for(fld <- msg.getFields) {
          pw.println("private FieldDescriptor %s;".format(fldDescName(fld)))
        }
      }
    }

    /**
     * Prints Constructor
     * It initializes the fields.
     */
    def printConstructor(msgs: List[(Int, Descriptor, DataType)]): Unit = {
      pw.println("public Converters(Descriptor[] messages) {")
      for ((n, msg, _) <- msgs) {
        pw.println("%s = messages[%d];".format(msgDescName(msg), n))
        for(fld <- msg.getFields) {
          pw.println("%s = %s.findFieldByNumber(%d);".format(fldDescName(fld), msgDescName(msg), fld.getNumber))
        }
      }
      pw.println("}")
    }

    /**
     * Prints converter from byte string to tuple.
     */
    def printBytesToTuple(msg: Descriptor): Unit = {
      pw.println(
        "public %s func_%s(Helpers.ByteStringWrapper bytes) {\n".format(typeName(msg), msg.getName)  +
        "    try {\n" +
        "        DynamicMessage msg = DynamicMessage.parseFrom(%s, bytes.get());\n".format(msgDescName(msg)) +
        "        return %s(msg);\n".format(copyToFuncName(msg)) +
        "    } catch (com.google.protobuf.InvalidProtocolBufferException e) {\n" +
        "        return null;\n" +
        "    }\n" +
        "}")
    }

    /**
     * Prints converter from tuple to byte string.
     */
    def printTupleToBytes(msg: Descriptor): Unit = {
      pw.println(
        "public Helpers.ByteStringWrapper func_bytes(%s tuple) {\n".format(typeName(msg))  +
        "    Message msg = %s(tuple);\n".format(copyFromFuncName(msg)) +
        "    return new Helpers.ByteStringWrapper(msg.toByteString());\n" +
        "}")
    }

    /**
     * Gets field data type.
     */
    def fldType(contents: DataType, fld: FieldDescriptor): String = {
      if (isMessage(fld)) {
        contents match  {
          case ArrayType(contents) => 
            fld.getMessageType.getFields.toList match {
              case List(elemsFld) => "List<%s>".format(fldType(contents, elemsFld))
              case _              => error("Invalid Array type.")
            }
          case _ => 
            typeName(fld.getMessageType)
        }
      } else {
        getDataType(fld)
      }
    }

    /**
     * Prints converter from ProtocolBuffers message to tuple.
     */
    def printMessageToTuple(msg: Descriptor, dataType: DataType): Unit = {

      pw.println("private %s %s(Message msg) {".format(typeName(msg), copyToFuncName(msg)))
      pw.println("%s tuple = new %s();".format(typeName(msg), typeName(msg)))

      val members = getMembers(dataType)

      for (fld <- msg.getFields) {
        // When no value is in optional field, Message#getFields() returns default value.
        members(fld.getName) match {
          case ArrayType(contents) =>
            require(fld.isRepeated)

            val elems = contents match {
              case ArrayType(_) => ".elems"
              case _ => ""
            }

            pw.println("tuple.%s = new ArrayList<%s>(%s);".format(
              fld.getName, fldType(contents, fld),
              "msg.getRepeatedFieldCount(%s)".format(fldDescName(fld))))
            pw.println(
              "if (msg.getRepeatedFieldCount(%s) > 0) {".format(fldDescName(fld)) +
              "  for (Object i: (List<?>)msg.getField(%s)) {\n".format(fldDescName(fld)) +
              "    tuple.%s.add(%s%s);\n".format(fld.getName, convertFromPBValue(fld, "i"), elems) +
              "  }" +
              "}")

          case _ => 
            require(!fld.isRepeated)
            pw.println("tuple.%s = %s;".format(
              fld.getName, 
              convertFromPBValue(fld, "msg.getField(%s)".format(fldDescName(fld)))))
        }
      }

      pw.println("return tuple;")
      pw.println("}")
    }

    /**
     * Prints converter from tuple to ProtocolBuffers message.
     */
    def printTupleToMessage(msg: Descriptor, dataType: DataType): Unit = {
      pw.println("private Message %s(%s tuple) {".format(copyFromFuncName(msg), typeName(msg)))
      pw.println("DynamicMessage.Builder builder = DynamicMessage.newBuilder(%s);".format(msgDescName(msg)))

      val members = getMembers(dataType)
      
      for (fld <- msg.getFields) {
        pw.println("if (tuple.%s != null) {".format(fld.getName))
        members(fld.getName) match {
          case ArrayType(contents) =>
            require(fld.isRepeated)
            val src = contents match {
              case _: ArrayType => 
                require(isMessage(fld))
                "new %s(i)".format(typeName(fld.getMessageType))
              case _ => "i"
            }
            pw.println(
              "for (%s i: tuple.%s) {\n".format(fldType(contents, fld), fld.getName) +
              "  builder.addRepeatedField(%s, %s);\n".format(fldDescName(fld), convertToPBValue(fld, src)) +
              "}")
          case _ =>
            require(!fld.isRepeated)
            val src = "tuple.%s".format(fld.getName)
            pw.println("builder.setField(%s, %s);".format(fldDescName(fld), convertToPBValue(fld, src)))
        }
        pw.println("}")
      }
      pw.println(";")
      pw.println("return builder.build();")
      pw.println("}")
    }

    // Start
    val typeMap = typeM.classMap.map { case (k, v) => (v, k) }
    val usedMsgs = msgs.zipWithIndex.flatMap { case (m, n) => 
      typeMap.get(m.getName) match {
        case Some(dataType) => Some((n, m, dataType))
        case None => None
      }
    }

    printFields(usedMsgs.map{_._2})
    printConstructor(usedMsgs)

    for ((n, msg, dataType) <- usedMsgs) {
      // Defines Converters
      printTupleToBytes(msg)
      printBytesToTuple(msg)
    
      printMessageToTuple(msg, dataType)
      printTupleToMessage(msg, dataType)
    }
  }

  /**
   * Generates code to convert ProtocolBuffers value to Sawzall Clone value.
   */
  private def convertFromPBValue(fld: FieldDescriptor, src: String): String = {
    import FieldDescriptor.JavaType._
    fld.getJavaType match {
      case BOOLEAN     => "((boolean) %s)? 1l : 0l".format(src)
      case BYTE_STRING => "(((Helpers.ByteStringWrapper) %s).get())".format(src)
      case DOUBLE      => "((Double) %s)".format(src)
      case ENUM        => "((EnumValueDescriptor) %s).getNumber()".format(src)
      case FLOAT       => "((Float) %s).doubleValue()".format(src)
      case INT         => "((Integer) %s).longValue()".format(src)
      case LONG        => "((Long) %s)".format(src)
      case MESSAGE     => "%s((Message)%s)".format(copyToFuncName(fld.getMessageType), src)
      case STRING      => "((String) %s)".format(src)
    }
  }

  /**
   * Generates code to convert Sawzall Clone value to ProtocolBuffers value.
   */
  private def convertToPBValue(fld: FieldDescriptor, src: String): String = {
    import FieldDescriptor.JavaType._
    fld.getJavaType match {
      case BOOLEAN     => "(%s != 0l)".format(src)
      case BYTE_STRING => src
      case DOUBLE      => src
      case ENUM        => "%s.getEnumType().findValueByNumber(%s)".format(fldDescName(fld), src)
      case FLOAT       => "(%s).floatValue()".format(src)
      case INT         => "(%s).intValue()".format(src)
      case LONG        => src
      case MESSAGE     => "%s(%s)".format(copyFromFuncName(fld.getMessageType), src)
      case STRING      => src
    }
  }

  /**
   * Gets Sawzall Clone data type  from FieldDescriptor.
   */
  private def getDataType(fld: FieldDescriptor) = {
    import FieldDescriptor.JavaType._
    fld.getJavaType match {
      case BOOLEAN     => "Long"
      case BYTE_STRING => "ByteString"
      case DOUBLE      => "Double"
      case ENUM        => "Integer"
      case FLOAT       => "Double"
      case INT         => "Long"
      case LONG        => "Long"
      case MESSAGE     => typeName(fld.getMessageType)
      case STRING      => "String"
    }
  }

  private def isMessage(desc: FieldDescriptor) = {
    desc.getJavaType == FieldDescriptor.JavaType.MESSAGE
  }

  private def msgDescName(desc: Descriptor): String = {
    "messageDesc_" + desc.getName
  }

  private def fldDescName(desc: FieldDescriptor): String = {
    "fldDesc_%s_%s".format(desc.getContainingType.getName, desc.getNumber)
  }

  private def copyToFuncName(desc: Descriptor): String = {
    "%s_copyToTuple".format(desc.getName)
  }

  private def copyFromFuncName(desc: Descriptor): String = {
    "%s_copyFromTuple".format(desc.getName)
  }

  private def typeName(desc: Descriptor): String = {
    "DataTypes.%s".format(desc.getName)
  }
}
